Skip to content

[TrimmableTypeMap] Remove ForceUnconditionalEntries workaround#11345

Open
simonrozsival wants to merge 20 commits into
mainfrom
dev/simonrozsival/remove-forceunconditionalentries
Open

[TrimmableTypeMap] Remove ForceUnconditionalEntries workaround#11345
simonrozsival wants to merge 20 commits into
mainfrom
dev/simonrozsival/remove-forceunconditionalentries

Conversation

@simonrozsival
Copy link
Copy Markdown
Member

@simonrozsival simonrozsival commented May 14, 2026

Summary

Remove the ForceUnconditionalEntries workaround from the trimmable typemap generator and fix the runtime type-resolution gap it was masking.

This restores conditional TypeMapAttribute entries for non-essential bindings while keeping interface-based Java objects marshalable to the most specific managed invoker type that survived trimming.

Background

ForceUnconditionalEntries was a temporary workaround for dotnet/runtime#127004. It forced all typemap entries to be emitted unconditionally, which prevented the trimmer from removing unused framework bindings.

When conditional entries were restored, TrustManagerFactory.GetTrustManagers() could return an ITrustManagerInvoker instead of an IX509TrustManagerInvoker in the trimmable typemap path. The preserved managed entry for javax/net/ssl/X509TrustManager was not reachable because the runtime only walked the Java class hierarchy; Java interfaces were not considered.

Fix

  • Remove ForceUnconditionalEntries so non-essential MCW entries are emitted conditionally again.
  • Add an interface-aware fallback in TrimmableTypeMap when the requested target type is an interface.
  • Walk each Java class in the hierarchy and recursively inspect directly declared Java interfaces from Class.getInterfaces().
  • Cache interface lookup results by (className, targetType), including misses.
  • Use JniEnvironment/JniObjectReference APIs with explicit disposal for JNI references.
  • Keep TargetTypeMatches() based on Java assignability checks so the linker does not need to preserve all managed interface metadata.

Other changes

  • Keep the [DynamicDependency] attributes on FindX509TrustManager; both typemap paths need the invoker types kept alive.
  • Remove the obsolete API 21-23 Conscrypt TrustManagerImpl workaround.
  • Rename the generated enum from AppFunctionState to AppFunctionEnabledState to avoid the Android API 37 android.app.appfunctions.AppFunctionState name collision.
  • Update typemap model and PE blob tests for restored conditional-entry behavior.
  • Add TrustManagerFactory_GetTrustManagers_ReturnsIX509TrustManager coverage.
  • Add Java fixtures for derived-interface marshalling (ExtendedValueProvider and InterfaceMarshalling). Their C# APIs are generated from @(AndroidJavaSource) bindings and are used by JavaInterfaceLookup_BaseInterfaceReturnType_UsesDerivedInterfaceProxy.
  • Update InstallAndroidDependenciesTest now that the Xamarin manifest contains the requested component.
  • Remove InterfaceProxyLookup_DoesNotLeakGlobalRefs for now because unrelated global-reference growth can poison the result.

Validation

Covered by the typemap generator tests, typemap runtime tests, and Mono.Android.NET device tests in CI. The direct gref-counting regression test was intentionally removed until the unrelated global-reference growth can be isolated.

Restore conditional TypeMap entries for non-essential MCW bindings now that the runtime trimmer issue has been fixed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 14, 2026 08:56
@simonrozsival simonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels May 14, 2026
@simonrozsival simonrozsival changed the title Remove ForceUnconditionalEntries workaround [TrimmableTypeMap] Remove ForceUnconditionalEntries workaround May 14, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR removes the ForceUnconditionalEntries workaround from trimmable typemap model generation, restoring conditional 3-arg TypeMapAttribute emission for non-essential MCW bindings while keeping essential runtime/user ACW entries unconditional.

Changes:

  • Removes the global force-unconditional switch from ModelBuilder.
  • Restores conditional target references for non-essential MCW entries.
  • Updates model and PE blob tests to expect restored 3-arg behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs Removes workaround logic and reverts unconditional decisions to IsUnconditionalEntry() plus alias-specific rules.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs Updates assertions and test names/comments for restored conditional MCW typemap entries.

simonrozsival and others added 2 commits May 15, 2026 15:30
…e name collision

- Replace [DynamicDependency] attributes on FindX509TrustManager with a
  HackToPreserveInvokers method using [MethodImpl(NoInlining)] to prevent
  the linker from trimming IX509TrustManagerInvoker and
  X509ExtendedTrustManagerInvoker.
- Remove obsolete API 21-23 Conscrypt TrustManagerImpl workaround.
- Rename generated enum from AppFunctionState to AppFunctionEnabledState
  to avoid name collision with the new Android API 37
  android.app.appfunctions.AppFunctionState class.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival marked this pull request as draft May 16, 2026 08:58
simonrozsival and others added 11 commits May 18, 2026 05:22
The isinst instruction properly preserves conditional TypeMap entries for
interfaces through the MarkType -> MarkRequirementsForInstantiatedTypes ->
ProcessType chain in ILLink. The typeof() and [DynamicDependency] additions
were based on an incorrect analysis that ProcessType was skipped for
interfaces - while true in the isinst handler's inner switch, MarkType
itself calls MarkRequirementsForInstantiatedTypes for interfaces which
calls ProcessType without any interface skip.

Verified with both ILLink and NativeAOT using a multi-assembly repro
that mirrors the Android typemap assembly layout.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When the getSuperclass() class hierarchy walk fails to find a
targetType-compatible TypeMap proxy (e.g., because an intermediate
class entry like X509ExtendedTrustManager was trimmed), fall back
to walking Java interfaces via getInterfaces() at each level of
the class hierarchy. This allows the runtime to find TypeMap entries
for Java interfaces (e.g., javax/net/ssl/X509TrustManager) that are
preserved by the trimmer but unreachable via getSuperclass() alone.

The interface walk only runs when targetType is an interface, keeping
the common path (class-based resolution) unchanged.

Also adds a device test TrustManagerFactory_GetTrustManagers_ReturnsIX509TrustManager
that verifies TrustManagerFactory.GetTrustManagers() returns elements
that can be cast to IX509TrustManager. This test runs on both
llvm-ir and trimmable typemap paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…es method ID

- Check interfaces inline during the getSuperclass() walk instead of
  a separate second pass, avoiding redundant GetObjectClass/getSuperclass calls
- Cache the JNI method ID for getInterfaces() in a static field
- Rename TryGetProxyFromInterfacesOfClass to TryMatchInterfaces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…aces scope

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ound

The [DynamicDependency] attributes on FindX509TrustManager are needed
by both llvm-ir and trimmable typemap paths to preserve invoker types.
Without them, the llvm-ir linker also trims X509ExtendedTrustManager
entries, causing the same IX509TrustManager resolution failure.

Removed the API 21-23 Conscrypt TrustManagerImpl workaround since
minimum supported API level is now higher.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address review feedback: add coverage that all-MCW alias groups emit
a conditional (3-arg) base alias-holder entry, mixed ACW/MCW alias
groups stay unconditional (2-arg), and essential runtime types remain
unconditional regardless of peer types.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace raw JNIEnv.GetMethodID/CallObjectMethod/GetArrayLength/
GetObjectArrayElement/DeleteLocalRef with their JniEnvironment
equivalents (JniMethodInfo, JniObjectReference, JniEnvironment.Arrays,
JniEnvironment.InstanceMethods). This matches the style of the
surrounding hierarchy walk code and uses proper ref disposal via
JniObjectReference.Dispose.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wrap the JNI class handle in Java.Lang.Class with DoNotTransfer
ownership and call the existing GetInterfaces() binding. This
reuses the JniPeerMembers method ID caching, thread-safe lookup,
and JNI remapping that the generated binding already handles.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… test

- Use DoNotTransfer | DoNotRegister when wrapping JNI class handles in
  Java.Lang.Class to avoid peer table registration overhead
- Remove extra blank line before GetProxyForJavaObject
- Remove verbose diagnostic logging from device test, keep only the
  assertion with descriptive failure message
- Remove unused Log import from test
- Keep hierarchy walk using JniObjectReference (not Java.Lang.Class) to
  avoid potential recursion into TypeMap during class resolution

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Java.Lang.Class.GetInterfaces() returns Java.Lang.Class[] where each
element holds a global ref. Dispose them in a finally block to avoid
leaking grefs until GC collection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival marked this pull request as ready for review May 18, 2026 14:14
simonrozsival and others added 6 commits May 19, 2026 15:06
Avoid wrapping Java class handles in temporary managed peers while matching Java interfaces in the trimmable typemap path. This prevents disposing shared class peers and adds regression coverage for the interface proxy lookup path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use JniEnvironment APIs consistently while walking Java interfaces and cache interface lookup results by Java class and target interface. This avoids repeated JNI reference churn in hot interface marshalling paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Include the trust-manager marshalling test in Mono.Android.NET-Tests and add an API-independent Java fixture that exercises base-interface return values whose concrete implementation advertises a derived interface.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add the missing Microsoft.Android.Runtime import so RuntimeFeature resolves in TrustManagerMarshallingTests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

copilot `copilot-cli` or other AIs were used to author this ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). trimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants